package business;

import java.io.EOFException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.io.StreamTokenizer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.List;
import java.util.Properties;
import java.util.StringTokenizer;
import java.util.logging.Level;
import java.util.logging.Logger;

import java.util.regex.Matcher;
import java.util.regex.Pattern;


/**
 *
 * @author Eduardo
 */
public class Index implements Serializable{

    public Hashtable<String, ArrayList<IndexItem>> index;
    private Hashtable<String, String> stopWord;
    public ArrayList<Document> documents;
    public ArrayList<String> files = new ArrayList<String>();
    private Stemmer stemmer;

    //Nombres de los ficheros para salvar y recuperar el indice
    public static final String fileIndexName = "index.obj";
    public static final String fileDocumentsName = "documents.obj";
    public static final String fileFilesName = "files.obj";

    

    public Index()
    {
        this.index = new Hashtable<String, ArrayList<IndexItem>>();
        this.documents = new ArrayList<Document>();
        loadStopWord();
        this.stemmer = new Stemmer();
    }

    /**
     * Cargamos las stopWord en una hash para luego utilizarlas en el tokenizador
     */
    private void loadStopWord()
    {
        try {
            stopWord = new Hashtable<String, String>();
            String namePROP = SM.class.getSimpleName().toLowerCase() + ".properties";
            Properties props = new Properties();
            props.load(SM.class.getResourceAsStream(namePROP));
            String filePath = props.getProperty("path");
            StreamTokenizer sTokenizer = new StreamTokenizer(new FileReader(filePath+"\\stopWord.txt"));
            while (sTokenizer.nextToken() != StreamTokenizer.TT_EOF)
                stopWord.put(sTokenizer.sval, sTokenizer.sval);
        }
        catch (IOException ex) {
            ex.printStackTrace();
        }
    }

    /**
     * Aniade las palabras importantes de un documento al indice del buscador     
     */
    public void addDocumentToIndex(Document doc, String text)
    {
        String sText, stemmerText;
        int position = 0;
        StringTokenizer sTokenizer = null;

        //Aniadimos el documento a la lista de documentos
        this.documents.add(doc);
        
        String cadenaTittle, cadenaBody;

        cadenaTittle = expRegularClean(doc.getTittle());
        cadenaBody = expRegularClean(text);

        //Tokenizamos el titulo y el texto del documento juntos
        sTokenizer = new StringTokenizer(cadenaTittle + " " + cadenaBody, " ");
        while(sTokenizer.hasMoreTokens())
        {
            sText = sTokenizer.nextToken();

            //PASO 1 -> Eliminamos las stopWords del Titulo
            if(!isStopWords(sText))
            {
                //PASO 2 -> Aplicamos la reduccion a la raiz o stemmer
                stemmerText = stemmer.getStemmer(sText);
                
                if(!index.containsKey(stemmerText))
                {                    
                    ArrayList<IndexItem> a = new ArrayList<IndexItem>();                    
                    a.add(new IndexItem(doc.getId(), position));
                    index.put(stemmerText, a);
                }
                else
                {
                    ArrayList<IndexItem> list = index.get(stemmerText);
                    boolean docExistsInArray = false;
                    for(IndexItem item : list) {
                        if(item.getDocReference() == doc.getId()) {
                            item.addOcurence();
                            docExistsInArray = true;
                        }
                    }
                    //Si el documento no tiene referencia dentro del array, hay que crear un item nuevo
                    if(!docExistsInArray)   
                        index.get(stemmerText).add(new IndexItem(doc.getId(), position));                    
                }

                position += sText.length() + 1;
            }
            else
                position += sText.length() + 1;
        }
    }
    

    /**
     * Recibe los terminos de busqueda, se tokenizan, eliminan stopWord y aplican stemmer,
     * y retorna los IndexItem de donde aparece.
     */
    public List search(String searchWord)
    {
        List resultList = new ArrayList();
        ArrayList<String> cleanQuery = new ArrayList<String>();
        ArrayList<IndexItem> matchDocuments = new ArrayList<IndexItem>(); //Lista de documentos coincidentes
        //Hashtable similitudeList = new Hashtable();                              //Almacena las simulitudes de cada doc con la query


        //cleanQuery = getCleanQuery(searchWord);
        cleanQuery = getCleanQuery(expRegularClean(searchWord));
        //Calculamos la funcion de simulitud de la query y los documentos

        //Realizamos la union de todos los documentos en los aparecen las palabras de la query
        for(String w : cleanQuery)
        {
            ArrayList<IndexItem> list = index.get(w);
            if(list != null)
            {
                for(IndexItem i : list) {
                    if(!documentInList(matchDocuments, i.getDocReference()))
                       matchDocuments.add(i);
                }
            }
            //else
                //return null;
        }

        //Calculamos la similitud
        for(IndexItem item : matchDocuments)
        {
            double sim = 0;
            for(String w : cleanQuery)
                sim += calculateIDF(w) * calculateWeight(w, item.getDocReference());            

            //Aniadimos una clase especial para que este tanto el valor como el item al que corresponde
            if(sim == 0.0)
                resultList.add(new IndexItemSimulitude(item, 0.0));
            else
                resultList.add(new IndexItemSimulitude(item, sim / normalize(cleanQuery, item.getDocReference())));

        }

        //Ordenamos de mayor a menor simulitud para ofrecer el ranking de documentos
        Collections.sort(resultList);
        Collections.reverse(resultList);

        return resultList;
    }


    public ArrayList<String> getCleanQuery(String searchWord)
    {
        ArrayList<String> cleanQuery = new ArrayList<String>();
        StringTokenizer sTokenizer = new StringTokenizer(searchWord, " ");
        while(sTokenizer.hasMoreElements())
        {
            String word = sTokenizer.nextToken();
            
            //@TODO Igle - Comprobar que se quitan las comas de la searchWord
            if(!isStopWords(expRegularClean(word))){
                word=expRegularClean(word);
                //word = stemmer.getStemmer(word).substring(0, word.length() - 1);
                cleanQuery.add(stemmer.getStemmer(word));
            }
        }

        return cleanQuery;
    }


    /**
     * Calcula el denominador de la funcion de similitud (Productos escalares |c| y |d|)
     */
    private double normalize(ArrayList<String> query, int idDoc)
    {
        double c = 0, d = 0;

        //Calculamos la |c|
        for(String q : query)
            c += Math.pow(calculateIDF(q), 2);
        c = Math.sqrt(c);

        //Calculamos la |d|
        Enumeration<String> wordIndexedList = index.keys();
        while(wordIndexedList.hasMoreElements())
        {
            String wordIndexed = wordIndexedList.nextElement();
            d += Math.pow(calculateWeight(wordIndexed, idDoc), 2);
        }
        d = Math.sqrt(d);

        return c * d;
    }

    /**
     * Calcula el peso de un termino en el documento
     */
    private double calculateWeight(String word, int idDocument)
    {
        ArrayList<IndexItem> list = index.get(word);
        double tf = 0;
        if(list != null)
        {
            for (IndexItem item : list) {
                if(item.getDocReference() == idDocument) {
                    tf = item.getOcurrences();
                    break;
                }
            }
            return tf * calculateIDF(word);
        }
        else
            return 0;
    }

    /**
     * Calculo del idf de una palabra
     */
    private double calculateIDF(String word)
    {
        double s = 0.0;
        if(index.get(word) != null)
            s = Math.log10((double)documents.size() / (double)index.get(word).size());
        return s;
    }

    /**
     * Comprueba si un documento esta en una lista
     */
    private boolean documentInList(ArrayList<IndexItem> list, int idDoc)
    {
        for(IndexItem item : list) {
            if(item.getDocReference() == idDoc)
                return true;
        }
        return false;
    }



    
    /**
     * Recibe una cadena y retorna si es o no una stopWord
     */
    public boolean isStopWords(String sText)
    {        
        return isNumeric(sText) || expRegularNumeric(sText) || isChars(sText) || stopWord.containsKey(sText);
    }

    /**
     * Calcula si un string representa a un numero
     */
    private boolean isNumeric(String cadena){
	try {
		Integer.parseInt(cadena);
		return true;
	} catch (NumberFormatException nfe){
		return false;
	}
    }

    /**
     * No mete en el indice cadenas menores de 3 caracteres
     */
    private boolean isChars(String cadena){
	try {
                if(cadena.length()<3)
                    return true;
                else
                    return false;
	} catch (NumberFormatException nfe){
		return false;
	}
    }

    /************************************************************************************************************************************
    //@TODO COMPRUEBA QUE LA CADENA NO CONTENGA CARACTERES PROHIBIDOS, en este caso NUMEROS Y ' . '
     /**
     * No mete cadenas que contengan numeros
     */
    
    private boolean expRegularNumeric (String cadena){
        try {
        //Pattern p = Pattern.compile("[^A-Za-z0-9\\.\\@_\\-~#]+");
        Pattern p = Pattern.compile("[0-9]");
        Matcher m = p.matcher(cadena);

        if(m.find()) 
            {return true;}
        else
            {return false;}
        }
        catch (NumberFormatException nfe){
		return false;
	}
    }

     /**
     * Expresion regular que quita los acentos
     *

    private boolean expRegularAcent (String cadena){
        try {
        Pattern p = Pattern.compile("[']");
        Matcher m = p.matcher(cadena);

        if(m.find())
            {
            cadena = cadena.replaceAll("'","");
            return true;
            }
            else
                {return false;}
        }
        catch (NumberFormatException nfe){
		return false;
	}
    }*/

     /**
     * Expresion regular que quita los caracteres que no nos interesan
     * en las expresiones
     */
    private String expRegularClean (String cadena){
        try {

            //@TODO Igle - Pinchito aqui
            cadena = cadena.replaceAll("'"," ");
            cadena = cadena.replaceAll("\\."," ");
            cadena = cadena.replaceAll(","," ");
            cadena = cadena.replaceAll(":"," ");
            cadena = cadena.replaceAll(";"," ");
            cadena = cadena.replaceAll("\\("," ");
            cadena = cadena.replaceAll("\\)"," ");
            cadena = cadena.replaceAll("/"," ");
            cadena = cadena.replaceAll("-\r\n","");
            cadena = cadena.replaceAll("\r\n"," ");
            cadena = cadena.replaceAll("\\+"," ");
            cadena = cadena.replaceAll("<"," ");
            cadena = cadena.replaceAll(">"," ");
            cadena = cadena.replaceAll("\r\n"," ");
            cadena = cadena.replaceAll("\n"," ");
            cadena = cadena.replaceAll("\r"," ");
            cadena = cadena.replaceAll("-"," ");
            cadena = cadena.replaceAll("¿"," ");
            //cadena = cadena.replaceAll("?"," ");           
            cadena = cadena.replaceAll("\""," ");
            return cadena;

        }
        catch (NumberFormatException nfe){
		return cadena;
	}
    }

//******************************************************************************************************************************************
    
    @Override
    public String toString()
    {
        StringBuffer text = new StringBuffer(400);        
        Enumeration<String> keys =  index.keys();
        String key;
        while(keys.hasMoreElements())
        {
            key = keys.nextElement();
            text.append("WORD("+key+")");
            ArrayList<IndexItem> values = index.get(key);
            for(IndexItem value : values)
                text.append("ITEM("+value.toString()+")");
            text.append("\n");
        }
        return text.toString();
    }

    private String documentToString()
    {
        StringBuffer text = new StringBuffer(400);
        for(Document doc : documents)
            text.append(doc.toString() + "\n");
        return text.toString();
    }

    public Document getDocumentById(int idDoc)
    {
        for(Document d : this.documents) {
            if (d.getId() == idDoc)
                return d;
        }
        return null;
    }




    public void saveIndex()
    {
        String namePROP = SM.class.getSimpleName().toLowerCase() + ".properties";
        Properties props = new Properties();
        try {
            props.load(SM.class.getResourceAsStream(namePROP));
        } catch (IOException ex) {
            ex.printStackTrace();
        }
        String filePath = props.getProperty("path");

        ObjectOutputStream file = null;
        try
        {
            //Almacenamos el fichero con los identificadores de los ficheros indexados
            File f = new File(filePath+"\\"+fileFilesName);
            if(f != null) f.delete();

            file = new ObjectOutputStream(new FileOutputStream(filePath+"\\"+fileFilesName));
            for(int i=0; i<files.size(); i++)
                file.writeUTF(files.get(i));
            file.close();
        }
        catch (IOException ex) {
            ex.printStackTrace();
        } finally {
            try {
                file.close();
            } catch (IOException ex) {
                ex.printStackTrace();
            }
        }

        try
        {
            File f2 = new File(filePath+"\\"+fileIndexName);
            if(f2 != null) f2.delete();
            
            file = new ObjectOutputStream(new FileOutputStream(filePath+"\\"+fileIndexName));
            file.writeObject(this.toString());
            file.close();
        }
        catch (IOException ex) {
            ex.printStackTrace();
        } finally {
            try {
                file.close();
            } catch (IOException ex) {
                ex.printStackTrace();
            }
        }


        try
        {
            File f2 = new File(filePath+"\\"+fileDocumentsName);
            if(f2 != null) f2.delete();

            file = new ObjectOutputStream(new FileOutputStream(filePath+"\\"+fileDocumentsName));
            file.writeObject(documentToString());
            file.close();
        }
        catch (IOException ex) {
            ex.printStackTrace();
        } finally {
            try {
                file.close();
            } catch (IOException ex) {
                ex.printStackTrace();
            }
        }
    }

    public void loadIndex()
    {
        ObjectInputStream file = null;

        String namePROP = SM.class.getSimpleName().toLowerCase() + ".properties";
        Properties props = new Properties();
        try {
            props.load(SM.class.getResourceAsStream(namePROP));
        } catch (IOException ex) {
            ex.printStackTrace();
        }
        String filePath = props.getProperty("path");

        try
        {
            file = new ObjectInputStream(new FileInputStream(filePath+"\\"+fileFilesName));
            files = new ArrayList<String>();

            while(true)           
                files.add((String)file.readUTF());
        }
        catch (EOFException end){}
        catch (Exception ex) {
            ex.printStackTrace();
        } finally {
            try {
                file.close();
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }

        try
        {
            file = new ObjectInputStream(new FileInputStream(filePath+"\\"+fileIndexName));
            String text = (String)file.readObject();

            //Reconstruimos el fichero a partir de la cadena de entrada
            String[] lines = text.split("WORD");
            for(int i=1; i<lines.length; i++)
            {
                String line = lines[i];                
                int posBraceRight = line.indexOf(")");
                String word = line.substring(1, posBraceRight);                
                line = line.substring(posBraceRight+1);


                ArrayList<IndexItem> list = new ArrayList<IndexItem>();
                String[] items = line.split("ITEM");
                for(int j=1; j<items.length; j++)
                {
                    String item = items[j];
                    item = item.substring(1);
                    int posBraceLeft = item.indexOf("(");
                    int docReference = Integer.parseInt(item.substring(0, posBraceLeft));
                    item = item.substring(posBraceLeft+1);

                    posBraceRight = item.indexOf("))");
                    item = item.substring(0, posBraceRight);
                    int ocurrences = Integer.parseInt(item);

                    IndexItem ii = new IndexItem(docReference);
                    ii.setOcurrences(ocurrences);
                    list.add(ii);
                }

                //Aniadimos al indice
                index.put(word, list);
            }
            file.close();
        }
        catch (Exception ex) {
            ex.printStackTrace();
        } finally {
            try {
                file.close();
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }



        try
        {
            file = new ObjectInputStream(new FileInputStream(filePath+"\\"+fileDocumentsName));
            String text = (String)file.readObject();

            //Reconstruimos el fichero a partir de la cadena de entrada
            String[] lines = text.split("DOC");
            for(int i=1; i<lines.length; i++)
            {
                Document doc = new Document();
                String line = lines[i];
                int pos = line.indexOf("##");
                doc.setId(Integer.parseInt(line.substring(1, pos)));
                line = line.substring(pos+1);

                pos = line.indexOf("##");
                doc.setFileId(Integer.parseInt(line.substring(1, pos)));
                line = line.substring(pos+1);

                pos = line.indexOf("##");
                doc.setTittle(line.substring(1, pos));
                line = line.substring(pos+1);

                pos = line.indexOf("##");
                doc.setInitialPosicion(Integer.parseInt(line.substring(1, pos)));
                line = line.substring(pos+1);

                pos = line.indexOf(")");
                doc.setLength(Integer.parseInt(line.substring(1, pos)));
                line = line.substring(pos+1);

                //Aniadimos a la lista de documentos
                documents.add(doc);
            }
            file.close();
        }
        catch (Exception ex) {
            ex.printStackTrace();
        } finally {
            try {
                file.close();
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }

    }

    public boolean isEmpty()
    {
        return documents.size() == 0;
    }
}
